package com.emeraldparser.util;

import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Collections2.transform;

import java.util.Collection;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;

public abstract class AbstractGraph<V, E> implements Graph<V, E> {

	@Override
	public abstract Collection<V> vertices();

	@Override
	public abstract Collection<Edge<V, E>> edges();

	@Override
	public boolean addVertex(V vertex) {
		return vertices().add(vertex);
	}

	@Override
	public boolean containsVertex(Object o) {
		return vertices().contains(o);
	}

	@Override
	public boolean containsEdge(Edge<?, ?> edge) {
		return edges().contains(edge);
	}

	@Override
	public Collection<Edge<V, E>> edges(final V vertex) {
		return filter(
				edges(),
				new Predicate<Edge<V, E>>() {
					@Override public boolean apply(Edge<V, E> edge) {
						return edge.touches(vertex);
					}
				}
			);
	}

	@Override
	public Collection<Edge<V, E>> edges(final V source, final V destination) {
		return filter(
				edges(source),
				new Predicate<Edge<V, E>>() {
					@Override public boolean apply(Edge<V, E> edge) {
						return edge.isBetween(source, destination);
					}
				}
			);
	}

	@Override
	public Collection<E> edgeLabels() {
		return transform(
				edges(),
				new Function<Edge<V, E>, E>() {
					@Override public E apply(Edge<V, E> edge) {
						return edge.getLabel();
					}
				}
			);
	}

	@Override
	public Collection<Edge<V, E>> inboundEdges(final V vertex) {
		return filter(
				edges(vertex),
				new Predicate<Edge<V, E>>() {
					@Override public boolean apply(Edge<V, E> edge) {
						return edge.goesTo(vertex);
					}
				}
			);
	}

	@Override
	public Collection<Edge<V, E>> inboundEdges(final V vertex, final E edgeLabel) {
		return filter(
				inboundEdges(vertex),
				new Predicate<Edge<V, E>>() {
					@Override public boolean apply(Edge<V, E> edge) {
						return Objects.equal(edgeLabel, edge.getLabel());
					}
				}
			);
	}

	@Override
	public Collection<Edge<V, E>> outboundEdges(final V vertex) {
		return filter(
				edges(vertex),
				new Predicate<Edge<V, E>>() {
					@Override public boolean apply(Edge<V, E> edge) {
						return edge.comesFrom(vertex);
					}
				}
			);
	}

	@Override
	public Collection<Edge<V, E>> outboundEdges(final V vertex, final E edgeLabel) {
		return filter(
				outboundEdges(vertex),
				new Predicate<Edge<V, E>>() {
					@Override public boolean apply(Edge<V, E> edge) {
						return Objects.equal(edgeLabel, edge.getLabel());
					}
				}
			);
	}

	@Override
	public Collection<E> inboundEdgeLabels(final V vertex) {
		return transform(
				inboundEdges(vertex),
				new Function<Edge<V, E>, E>() {
					@Override public E apply(Edge<V, E> edge) {
						return edge.getLabel();
					}
				}
			);
	}

	@Override
	public Collection<E> outboundEdgeLabels(final V vertex) {
		return transform(
				outboundEdges(vertex),
				new Function<Edge<V, E>, E>() {
					@Override public E apply(Edge<V, E> edge) {
						return edge.getLabel();
					}
				}
			);
	}

	@Override
	public Collection<V> predecessors(final V vertex) {
		return transform(
				inboundEdges(vertex),
				new Function<Edge<V, E>, V>() {
					@Override public V apply(Edge<V, E> edge) {
						return edge.opposite(vertex);
					}
				}
			);
	}

	@Override
	public Collection<V> predecessors(final V vertex, final E edgeLabel) {
		return transform(
				inboundEdges(vertex, edgeLabel),
				new Function<Edge<V, E>, V>() {
					@Override public V apply(Edge<V, E> edge) {
						return edge.opposite(vertex);
					}
				}
			);
	}

	@Override
	public Collection<V> successors(final V vertex) {
		return transform(
				outboundEdges(vertex),
				new Function<Edge<V, E>, V>() {
					@Override public V apply(Edge<V, E> edge) {
						return edge.opposite(vertex);
					}
				}
			);
	}

	@Override
	public Collection<V> successors(final V vertex, final E edgeLabel) {
		return transform(
				outboundEdges(vertex, edgeLabel),
				new Function<Edge<V, E>, V>() {
					@Override public V apply(Edge<V, E> edge) {
						return edge.opposite(vertex);
					}
				}
			);
	}

	@Override
	public int hashCode() {
		return Objects.hashCode(vertices(), edges());
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		
		if (obj == null) {
			return false;
		}
		
		if (!(obj instanceof Graph<?, ?>)) {
			return false;
		}
		
		Graph<?, ?> other = (Graph<?, ?>) obj;
		
		return Objects.equal(vertices(), other.vertices())
		    && Objects.equal(edges(), other.edges());
	}

	@Override
	public boolean addEdge(Edge<V, E> edge) {
		return edges().add(edge);
	}

	@Override
	public boolean addDirectedEdge(V source, V destination, E edgeLabel) {
		return edges().add(DirectedGraphEdge.create(source, destination, edgeLabel));
	}

	@Override
	public boolean addUndirectedEdge(V source, V destination, E edgeLabel) {
		return edges().add(UndirectedGraphEdge.create(source, destination, edgeLabel));
	}

	@Override
	public boolean addAll(Graph<? extends V, ? extends E> graph) {
		boolean modified = false;
		modified |= vertices().addAll(graph.vertices());
		//modified |= edges().addAll(graph.edges());
		
		for (Edge<? extends V, ? extends E> edge : graph.edges()) {
			if (edge.isDirected()) {
				modified |= addDirectedEdge(edge.getSource(), edge.getDestination(), edge.getLabel());
			} else {
				modified |= addUndirectedEdge(edge.getSource(), edge.getDestination(), edge.getLabel());
			}
		}
		
		return modified;
	}

}
